/*-------------------------------------------------------------------------
ParserGen.cs -- Generation of the Recursive Descent Parser
Compiler Generator Coco/R,
Copyright (c) 1990, 2004 Hanspeter Moessenboeck, University of Linz
extended by M. Loeberbauer & A. Woess, Univ. of Linz
extended (June 2004) by Pat Terry, Rhodes University (p.terry@ru.ac.za)
(changes marked as "pdt")

This program is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.

This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
for more details.

You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

As an exception, it is allowed to write an extension of Coco/R that is
used as a plugin in non-free software.

If not otherwise stated, any source code generated by Coco/R (other than
Coco/R itself) does not fall under the GNU General Public License.
-------------------------------------------------------------------------*/
using System;
using System.IO;
using System.Collections;
using System.Text;

namespace at.jku.ssw.Coco {

public class ParserGen {

	const int maxTerm = 3;   // sets of size < maxTerm are enumerated
	const char CR  = '\r';
	const char LF  = '\n';
	const char TAB = '\t';
	const int  EOF = -1;

	const int tErr = 0;      // error codes
	const int altErr = 1;
	const int syncErr = 2;

	public static Position usingPos; // "using" definitions from the attributed grammar

	static int errorNr;       // highest parser error number
	static Symbol curSy;      // symbol whose production is currently generated
	static FileStream fram;   // parser frame file
	static StreamWriter gen;  // generated parser source file
	static StringWriter err;  // generated parser error messages
	static string srcName;    // name of attributed grammar file
	static string srcDir;     // directory of attributed grammar file
	static ArrayList symSet = new ArrayList();

	static void Indent (int n) {
		for (int i = 1; i <= n; i++) gen.Write('\t');
	}

	static bool Overlaps(BitArray s1, BitArray s2) {
		int len = s1.Count;
		for (int i = 0; i < len; ++i) {
			if (s1[i] && s2[i]) {
				return true;
			}
		}
		return false;
	}

	// use a switch if more than 5 alternatives and none starts with a resolver
	static bool UseSwitch (Node p) {
		BitArray s1, s2;
		if (p.typ != Node.alt) return false;
		int nAlts = 0;
		s1 = new BitArray(Symbol.terminals.Count);
		while (p != null) {
			s2 = Tab.Expected0(p.sub, curSy);
			// must not optimize with switch statement, if there are ll1 warnings
			if (Overlaps(s1, s2)) { return false; }
			s1.Or(s2);
			++nAlts;
			// must not optimize with switch-statement, if alt uses a resolver expression
			if (p.sub.typ == Node.rslv) return false;
			p = p.down;
		}
		return nAlts > 5;
	}

	static void CopyFramePart (string stop) { /* pdt */
		int last = 0;
		char startCh = stop[0];
		int endOfStopString = stop.Length-1;
		int ch = fram.ReadByte();
		while (ch != EOF)
			if (ch == startCh) {
				int i = 0;
				do {
					if (i == endOfStopString) return; // stop[0..i] found
					ch = fram.ReadByte(); i++;
				} while (ch == stop[i]);
				// stop[0..i-1] found; continue with last read character
				gen.Write(stop.Substring(0, i));
			} else if (ch == LF) { if (last != CR) gen.WriteLine(); last = ch; ch = fram.ReadByte();
			} else if (ch == CR) { gen.WriteLine(); last = ch; ch = fram.ReadByte();
			} else {
				gen.Write((char)ch); last = ch; ch = fram.ReadByte();
			}
		Errors.Exception(" -- incomplete or corrupt parser frame file");
	}

	private static void CopySourcePart(Position pos, int indent) {
		// Copy text described by pos from atg to gen
		/* pdt - major changes from original */
		int ch, lastCh, nChars, i, col;
		if (pos != null) {
			Buffer.Pos = pos.beg;
			ch = ' ';
			nChars = pos.len;
			col = pos.col - 1;
			while (nChars > 0 && (ch == ' ' || ch == TAB) ) {
			// skip over leading white space
				ch = Buffer.Read(); nChars--; col++;
			}
			Indent(indent);
			for (;;) {
				while (ch == CR || ch == LF) {
					// blank lines with correct number of leading blanks
					gen.WriteLine();
					lastCh = ch;
					if (nChars > 0) { ch = Buffer.Read(); nChars--;  } else goto done;
					if (ch == LF && lastCh == CR) { // must be MS-DOS
						if (nChars > 0) { ch = Buffer.Read(); nChars--; } else goto done;
					}
					if (ch != CR && ch != LF) { // there must be something on this line
						Indent(indent);
						i = col - 1;
						while ((ch == ' ' || ch == TAB) && i > 0) {
						// skip at most "col - 1" white space at line start
							if (nChars > 0) { ch = Buffer.Read(); nChars--; } else goto done;
							i--;
						}
					}
				}
				i = 0;
				while (ch == ' ') {
					if (nChars > 0) { ch = Buffer.Read(); nChars--; } else goto done;
					i++;
				}
				if (ch != CR && ch != LF && ch != EOF)  {
					for (int j = 1; j <= i; j++) gen.Write(' ');
					gen.Write((char) ch);
					if (nChars > 0) { ch = Buffer.Read(); nChars--; } else goto done;
				}
			}
			done:
			if (indent > 0) gen.WriteLine();
		}
	}

/* old was as follows:
	static void CopySourcePart (Position pos, int indent) {
		// Copy text described by pos from atg to gen
		int ch, nChars, i;
		if (pos != null) {
			Buffer.Pos = pos.beg; ch = Buffer.Read(); nChars = pos.len - 1;
			Indent(indent);
			while (nChars >= 0) {
				while (ch == CR || ch == LF) {  // eol is either CR or CRLF or LF
					gen.WriteLine(); Indent(indent);
					if (ch == CR) { ch = Buffer.Read(); nChars--; }  // skip CR
					if (ch == LF) { ch = Buffer.Read(); nChars--; }  // skip LF
					for (i = 1; i <= pos.col && (ch == ' ' || ch == '\t'); i++) {
						// skip blanks at beginning of line
						ch = Buffer.Read(); nChars--;
					}
					if (i <= pos.col) pos.col = i - 1; // heading TABs => not enough blanks
					if (nChars < 0) goto done;
				}
				gen.Write((char)ch);
				ch = Buffer.Read(); nChars--;
			}
			done:
			if (indent > 0) gen.WriteLine();
		}
	}
*/

	static void GenErrorMsg (int errTyp, Symbol sym) {
		errorNr++;
		err.Write("\t\t\tcase " + errorNr + ": s = \"");
		switch (errTyp) {
			case tErr:
				if (sym.name[0] == '"') err.Write(DFA.Escape(sym.name) + " expected");
				else err.Write(sym.name + " expected");
				break;
			case altErr: err.Write("invalid " + sym.name); break;
			case syncErr: err.Write("this symbol not expected in " + sym.name); break;
		}
		err.WriteLine("\"; break;");
	}

	static int NewCondSet (BitArray s) {
		for (int i = 1; i < symSet.Count; i++) // skip symSet[0] (reserved for union of SYNC sets)
			if (Sets.Equals(s, (BitArray)symSet[i])) return i;
		symSet.Add(s.Clone());
		return symSet.Count - 1;
	}

	static void GenCond (BitArray s, Node p) {
		if (p.typ == Node.rslv) CopySourcePart(p.pos, 0);
		else {
			int n = Sets.Elements(s);
			if (n == 0) gen.Write("false"); // should never happen
			else if (n <= maxTerm)
				foreach (Symbol sym in Symbol.terminals) {
					if (s[sym.n]) {
						gen.Write("la.kind == "); PrintTermName(sym);
						--n;
						if (n > 0) gen.Write(" || ");
					}
				}
			else
				gen.Write("StartOf({0})", NewCondSet(s));
			/*
			if (p.typ == Node.alt) {
				// for { ... | IF ... | ... } or [ ... | IF ... | ... ]
				// check resolvers in addition to terminal start symbols of alternatives
				Node q = p;
				while (q != null) {
					if (q.sub.typ == Node.rslv) {
						gen.Write(" || ");
						CopySourcePart(q.sub.pos, 0);
					}
					q = q.down;
				}
			}
			*/
		}
	}

	static void PutCaseLabels (BitArray s) {
		foreach (Symbol sym in Symbol.terminals)
			if (s[sym.n]) {
				gen.Write("case "); PrintTermName(sym); gen.Write(": "); /* pdt */
			}
	}

	static void GenCode (Node p, int indent, BitArray isChecked) {
		Node p2;
		BitArray s1, s2;
		while (p != null) {
			switch (p.typ) {
				case Node.nt: {
					Indent(indent);
					gen.Write(p.sym.name + "(");
					CopySourcePart(p.pos, 0);
					gen.WriteLine(");");
					break;
				}
				case Node.t: {
					Indent(indent);
					if (isChecked[p.sym.n]) gen.WriteLine("Get();");
					else {
						gen.Write("Expect("); PrintTermName(p.sym); gen.WriteLine(");"); /* pdt */
					}
					break;
				}
				case Node.wt: {
					Indent(indent);
					s1 = Tab.Expected(p.next, curSy);
					s1.Or(Tab.allSyncSets);
					gen.Write("ExpectWeak("); PrintTermName(p.sym);  /* pdt */
					gen.WriteLine(", {0});", NewCondSet(s1));
					break;
				}
				case Node.any: {
					Indent(indent);
					gen.WriteLine("Get();");
					break;
				}
				case Node.eps: break;  // nothing
				case Node.rslv: break; // nothing
				case Node.sem: {
					CopySourcePart(p.pos, indent);
					break;
				}
				case Node.sync: {
					Indent(indent);
					GenErrorMsg(syncErr, curSy);
					s1 = (BitArray)p.set.Clone();
					gen.Write("while (!("); GenCond(s1, p); gen.Write(")) {");
					gen.Write("SynErr({0}); Get();", errorNr); gen.WriteLine("}");
					break;
				}
				case Node.alt: {
					s1 = Tab.First(p);
					bool equal = Sets.Equals(s1, isChecked);
					bool useSwitch = UseSwitch(p);
					if (useSwitch) { Indent(indent); gen.WriteLine("switch (la.kind) {"); }
					p2 = p;
					while (p2 != null) {
						s1 = Tab.Expected(p2.sub, curSy);
						Indent(indent);
						if (useSwitch) {
							PutCaseLabels(s1); gen.WriteLine("{");
						} else if (p2 == p) {
							gen.Write("if ("); GenCond(s1, p2.sub); gen.WriteLine(") {");
						} else if (p2.down == null && equal) { gen.WriteLine("} else {");
						} else {
							gen.Write("} else if (");  GenCond(s1, p2.sub); gen.WriteLine(") {");
						}
						s1.Or(isChecked);
						//if (p2.sub.typ == Node.rslv) GenCode(p2.sub.next, indent + 1, s1);
						//else GenCode(p2.sub, indent + 1, s1);
						GenCode(p2.sub, indent + 1, s1);
						if (useSwitch) {
							Indent(indent); gen.WriteLine("\tbreak;");
							Indent(indent); gen.WriteLine("}");
						}
						p2 = p2.down;
					}
					Indent(indent);
					if (equal) {
						gen.WriteLine("}");
					} else {
						GenErrorMsg(altErr, curSy);
						if (useSwitch) {
							gen.WriteLine("default: SynErr({0}); break;", errorNr);
							Indent(indent); gen.WriteLine("}");
						} else {
							gen.Write("} "); gen.WriteLine("else SynErr({0});", errorNr);
						}
					}
					break;
				}
				case Node.iter: {
					Indent(indent);
					p2 = p.sub;
					gen.Write("while (");
					if (p2.typ == Node.wt) {
						s1 = Tab.Expected(p2.next, curSy);
						s2 = Tab.Expected(p.next, curSy);
						gen.Write("WeakSeparator(");
						PrintTermName(p2.sym);  /* pdt */
						gen.Write(", {0}, {1})", NewCondSet(s1), NewCondSet(s2));
						s1 = new BitArray(Symbol.terminals.Count);  // for inner structure
						if (p2.up || p2.next == null) p2 = null; else p2 = p2.next;
					} else {
						s1 = Tab.First(p2);
						GenCond(s1, p2);
					}
					gen.WriteLine(") {");
					GenCode(p2, indent + 1, s1);
					Indent(indent); gen.WriteLine("}");
					break;
				}
				case Node.opt:
					//if (p.sub.typ == Node.rslv) s1 = Tab.First(p.sub.next);
					//else s1 = Tab.First(p.sub);
					s1 = Tab.First(p.sub);
					Indent(indent);
					gen.Write("if ("); GenCond(s1, p.sub); gen.WriteLine(") {");
					//if (p.sub.typ == Node.rslv) GenCode(p.sub.next, indent + 1, s1);
					//else GenCode(p.sub, indent + 1, s1);
					GenCode(p.sub, indent + 1, s1);
					Indent(indent); gen.WriteLine("}");
					break;
			}
			if (p.typ != Node.eps && p.typ != Node.sem && p.typ != Node.sync)
				isChecked.SetAll(false);  // = new BitArray(Symbol.terminals.Count);
			if (p.up) break;
			p = p.next;
		}
	}

	static void GenTokens(bool withNames) { /* pdt */
		foreach (Symbol sym in Symbol.terminals) {
			if (Char.IsLetter(sym.name[0]))
				gen.WriteLine("\tconst int _{0} = {1};", sym.name, sym.n);
		}
		if (withNames) {                       /* pdt */
			gen.WriteLine("\t// terminals");
			foreach (Symbol sym in Symbol.terminals) {
				gen.WriteLine("\tpublic const int {0} = {1};", sym.symName, sym.n);
			}
			gen.WriteLine("\t// pragmas");
			foreach (Symbol sym in Symbol.pragmas) {
				gen.WriteLine("\tpublic const int {0} = {1};", sym.symName, sym.n);
			}
			gen.WriteLine();
		}
	}

	static void GenPragmas() {
		foreach (Symbol sym in Symbol.pragmas) {
			gen.WriteLine("\tconst int _{0} = {1};", sym.name, sym.n);
		}
	}

	static void GenCodePragmas() {
		foreach (Symbol sym in Symbol.pragmas) {
			gen.Write("\t\t\t\tif (la.kind == ");
			PrintTermName(sym); gen.WriteLine(") {");  /* pdt */
			CopySourcePart(sym.semPos, 4);
			gen.WriteLine("\t\t\t\t}");
		}
	}

	static void GenProductions() {
		foreach (Symbol sym in Symbol.nonterminals) {
			curSy = sym;
			gen.Write("\tstatic void {0}(", sym.name);
			CopySourcePart(sym.attrPos, 0);
			gen.WriteLine(") {");
			CopySourcePart(sym.semPos, 2);
			GenCode(sym.graph, 2, new BitArray(Symbol.terminals.Count));
			gen.WriteLine("\t}"); gen.WriteLine();
		}
	}

	static void InitSets() {
		for (int i = 0; i < symSet.Count; i++) {
			BitArray s = (BitArray)symSet[i];
			gen.Write("\t\t{");
			int j = 0;
			foreach (Symbol sym in Symbol.terminals) {
				if (s[sym.n]) gen.Write("T,"); else gen.Write("x,");
				++j;
				if (j%4 == 0) gen.Write(" ");
			}
			if (i == symSet.Count-1) gen.WriteLine("x}"); else gen.WriteLine("x},");
		}
	}

	static void OpenGen(bool backUp) { /* pdt */
		try {
			string fn = srcDir + "Parser.cs"; /* pdt */
			if (File.Exists(fn) && backUp) File.Copy(fn, fn + ".old", true);
			gen = new StreamWriter(new FileStream(fn, FileMode.Create)); /* pdt */
		} catch (IOException) {
			Errors.Exception("-- Cannot generate parser file");
		}
	}

	public static void WriteParser (bool withNames) {/* pdt */
		symSet.Add(Tab.allSyncSets);
		string fr = srcDir + "Parser.frame";
		if (!File.Exists(fr)) {
			if (Tab.frameDir != null) fr = Tab.frameDir.Trim() + Path.DirectorySeparatorChar + "Parser.frame";
			if (!File.Exists(fr)) Errors.Exception("-- Cannot find Parser.frame");
		}
		try {
			fram = new FileStream(fr, FileMode.Open, FileAccess.Read, FileShare.Read);
		} catch (IOException) {
			Errors.Exception("-- Cannot open Parser.frame.");
		}
		OpenGen(true); /* pdt */
		if (withNames) Tab.AssignNames(); /* pdt */
		err = new StringWriter();
		foreach (Symbol sym in Symbol.terminals) GenErrorMsg(tErr, sym);
		CopyFramePart("-->begin");
		if (!srcName.ToLower().EndsWith("coco.atg")) {
			gen.Close(); OpenGen(false); /* pdt */
		}
		if (usingPos != null) {CopySourcePart(usingPos, 0); gen.WriteLine();}
		CopyFramePart("-->namespace");
		if (Tab.nsName != null && Tab.nsName.Length > 0) gen.Write(Tab.nsName); /* pdt */
		else gen.Write(Tab.gramSy.name);
		CopyFramePart("-->constants");
		GenTokens(withNames); /* ML 2002/09/07 write the token kinds */
		gen.WriteLine("\tconst int maxT = {0};", Symbol.terminals.Count-1);
		GenPragmas(); /* ML 2005/09/23 write the pragma kinds */
		CopyFramePart("-->declarations"); CopySourcePart(Tab.semDeclPos, 0);
		CopyFramePart("-->pragmas"); GenCodePragmas();
		CopyFramePart("-->productions"); GenProductions();
		CopyFramePart("-->parseRoot"); gen.WriteLine("\t\t{0}();", Tab.gramSy.name);
		gen.Write("\t\tExpect("); PrintTermName(Tab.eofSy); gen.WriteLine(");");
		CopyFramePart("-->initialization"); InitSets();
		CopyFramePart("-->errors"); gen.Write(err.ToString());
		CopyFramePart("$$$");
		gen.Close();
	}

	public static void WriteStatistics () {
		Trace.WriteLine();
		Trace.WriteLine("Statistics:"); /* pdt */
		Trace.WriteLine("-----------"); Trace.WriteLine();
		Trace.WriteLine("{0} terminals", Symbol.terminals.Count);
		Trace.WriteLine("{0} symbols", Symbol.terminals.Count + Symbol.pragmas.Count +
		                               Symbol.nonterminals.Count);
		Trace.WriteLine("{0} nodes", Node.nodes.Count);
		Trace.WriteLine("{0} sets", symSet.Count);
		Trace.WriteLine(); /* pdt */
	}

	public static void Init (string file, string dir) {
		srcName = file;
		srcDir = dir;
		errorNr = -1;
		usingPos = null;
	}

	public static void PrintTermName(Symbol sym) { /* pdt */
		if (sym.symName == null) gen.Write(sym.n); else gen.Write(sym.symName);
	}

} // end ParserGen

} // end namespace
